﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Forms;
using DevExpress.XtraEditors;
using CSW.Framework.Common;
using CSW.Framework.Linq;
using CSW.Framework.UI.Binding;
using CSW.Framework.UI.DevEx.UserControls;

namespace CSW.Framework.UI.DevEx
{
    /// <summary>
    /// Edit Grid Controller.  Type safe access to <see cref="DXGridControl"/>.
    /// </summary>
    /// <typeparam name="TBusinessObject">The business object item.</typeparam>
    public class EditGridController<TBusinessObject> : TypedGridControllerLinq<TBusinessObject>
        where TBusinessObject : class, IBusinessObjectLinq, new()
    {        
        /// <summary>
        /// DeleteListDelegate.
        /// </summary>
        /// <param name="list">The list.</param>
        public delegate void DeleteListDelegate(IEnumerable<TBusinessObject> list);

        /// <summary>
        /// Occurs when a new record is added to the grid via the embedded navigator. See <see cref="AddEditButtons(bool)"/>.
        /// </summary>
        public event NewRecordEventHandler<TBusinessObject> NewRecord;

        /// <summary>
        /// Occurs when enter is pressed on a row or a row is double clicked on the grid. See <see cref="AddEditButtons(bool)"/>.
        /// </summary>
        public event EditRecordEventHandler<TBusinessObject> EditRecord;

        /// <summary>
        /// Occurs when a record is deleted from the list.
        /// </summary>
        public event DeleteRecordsEventHandler<TBusinessObject> DeleteRecord;

        private ConstructorInfo m_FormConstructor;
        private RecordType? m_RecordType;
        private bool m_IsEditable = true;
        private bool m_AllowAdd = true;
        private bool m_AllowDelete = true;

        /*/// <summary>
        /// Initializes a new instance of the <see cref="EditGridController{TBusinessObject}"/> class.
        /// </summary>
        /// <param name="gridControl">The grid control.</param>
        public EditGridController(BaseGridControl gridControl)
            : this(gridControl, null, true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EditGridController{TBusinessObject}"/> class.
        /// </summary>
        /// <param name="gridControl">The grid control.</param>
        /// <param name="multiSelect">if set to <c>true</c>, multi select is enabled (default = true).</param>
        public EditGridController(BaseGridControl gridControl, bool multiSelect)
            : this(gridControl, null, multiSelect)
        {
        }*/

        /// <summary>
        /// Initializes a new instance of the <see cref="EditGridController{TBusinessObject}"/> class.
        /// </summary>
        /// <param name="gridControl">The grid control.</param>
        /// <param name="exportFileName">Name of the export file, minus the extension.</param>
        public EditGridController(DXGridControl gridControl, string exportFileName)
            : this(gridControl, exportFileName, true)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="EditGridController{TBusinessObject}"/> class.
        /// </summary>
        /// <param name="gridControl">The grid control.</param>
        /// <param name="exportFileName">Name of the export file, minus the extension.</param>
        /// <param name="multiSelect">if set to <c>true</c>, multi select is enabled (default = true).</param>
        public EditGridController(DXGridControl gridControl, string exportFileName, bool multiSelect)
            : base(gridControl, exportFileName, multiSelect)
        {
            Grid.RowDoubleClick += m_GridControl_RowDoubleClick;
            Grid.KeyPress += Grid_KeyPress;
            Grid.KeyDown += Grid_KeyDown;
            BestFitMaxRowCount = 20;
        }

        /// <summary>
        /// Adds add/remove buttons to the bottom of the grid. Uses the embedded navigator. See <see cref="EditRecord"/> and <see cref="NewRecord"/> events.
        /// </summary>
        public void AddEditButtons()
        {
            AddEditButtons(true);
        }

        /// <summary>
        /// Adds add/remove buttons to the bottom of the grid. Uses the embedded navigator. See <see cref="EditRecord"/> and <see cref="NewRecord"/> events.
        /// </summary>
        /// <param name="includeRecordCount">if set to <c>true</c> include the record count at the bottom of the grid. (default = true)</param>
        public void AddEditButtons(bool includeRecordCount)
        {
            Grid.UseEmbeddedNavigator = true;
            foreach (NavigatorButton button in Grid.EmbeddedNavigator.Buttons.ButtonCollection)
            {
                if (button.ButtonType == NavigatorButtonType.Append ||
                    button.ButtonType == NavigatorButtonType.Remove)
                {
                    button.Visible = true;
                }
                else if (button.ButtonType != NavigatorButtonType.Custom)
                {
                    button.Visible = false;
                }
            }

            if (includeRecordCount)
            {
                Grid.EmbeddedNavigator.TextStringFormat = "{1:#,0} record(s)";
                Grid.EmbeddedNavigator.TextLocation = NavigatorButtonsTextLocation.Begin;
            }

            Grid.EmbeddedNavigator.ButtonClick += EmbeddedNavigator_ButtonClick;
            GridView.OptionsView.ShowIndicator = true;
        }

        private void Grid_KeyDown(object sender, KeyEventArgs e)
        {
            if (!m_IsEditable)
                return;

            if (e.KeyCode == Keys.Insert)
            {
                if (!m_AllowAdd)
                    return;

                AddNewRecord();
            }
            else if (e.KeyCode == Keys.Delete)
            {
                if (!m_AllowDelete)
                    return;

                InternalDeleteRecord();
            }
        }

        private void Grid_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (!m_IsEditable)
                return;

            if (e.KeyChar == (int)Keys.Enter)
            {
                int? rowHandle = GetSelectedRowHandle();
                if (rowHandle != null)
                    SendEditRecord(rowHandle.Value);
            }
        }

        private void m_GridControl_RowDoubleClick(object sender, RowDoubleClickEventArgs e)
        {
            if (!m_IsEditable)
                return;

            SendEditRecord(e.RowHandle);
        }

        private void SendEditRecord(int rowHandle)
        {
            if (!m_IsEditable)
                return;

            EditRecordEventHandler<TBusinessObject> editRecord = EditRecord;
            if (editRecord != null)
            {
                Cursor.Current = Cursors.WaitCursor;

                TBusinessObject item = GetRow(rowHandle);

                Cursor.Current = Cursors.Default;

                editRecord(this, new EditRecordEventArgs<TBusinessObject>(item));

                Grid.RefreshDataSource();
            }
        }

        private void EmbeddedNavigator_ButtonClick(object sender, NavigatorButtonClickEventArgs e)
        {
            if (!m_IsEditable)
                return;

            if (e.Button.ButtonType == NavigatorButtonType.Append)
            {
                if (!m_AllowAdd)
                    return;

                AddNewRecord();
                e.Handled = true;
            }
            else if (e.Button.ButtonType == NavigatorButtonType.Remove)
            {
                if (!m_AllowDelete)
                    return;

                InternalDeleteRecord();
                e.Handled = true;
            }
            else if (e.Button.ButtonType == NavigatorButtonType.Edit)
            {
                int? rowHandle = GetSelectedRowHandle();
                if (rowHandle.HasValue)
                    SendEditRecord(rowHandle.Value);
                e.Handled = true;
            }
        }

        private void InternalDeleteRecord()
        {
            if (!m_IsEditable)
                return;

            if (!m_AllowDelete)
                return;

            DeleteRecordsEventHandler<TBusinessObject> deleteRecord = DeleteRecord;
            if (deleteRecord != null)
            {
                DeleteRecordsEventArgs<TBusinessObject> args = new DeleteRecordsEventArgs<TBusinessObject>(GetSelectedObjects());
                deleteRecord(this, args);
                if (args.Handled)
                    return;
            }

            if (m_RecordType == RecordType.Child)
            {
                DialogResult res = MessageBox.Show("Remove selected item(s)?", "", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
                if (res == DialogResult.Yes)
                {
                    foreach (TBusinessObject item in GetSelectedObjects())
                    {
                        DataSource.Remove(item);
                    }
                }
            }
            else
            {
                DeleteSelectedRows();
            }
        }

        private void AddNewRecord()
        {
            if (!m_IsEditable)
                return;

            if (!m_AllowAdd)
                return;

            NewRecordEventHandler<TBusinessObject> newRecord = NewRecord;
            if (newRecord != null)
            {
                Cursor.Current = Cursors.WaitCursor;

                TBusinessObject item = new TBusinessObject();
                if (!item.IsEditting)
                    item.BeginEdit(false);

                if (DataSource == null)
                    DataSource = new BindingList<TBusinessObject>();

                DataSource.Add(item);

                Cursor.Current = Cursors.Default;

                bool removeItem = true;
                try
                {
                    NewRecordEventArgs<TBusinessObject> newRecordArgs = new NewRecordEventArgs<TBusinessObject>(item);
                    newRecord(this, newRecordArgs);
                    removeItem = newRecordArgs.Remove;
                }
                finally
                {
                    if (removeItem)
                    {
                        Cursor.Current = Cursors.WaitCursor;
                        DataSource.Remove(item);
                        Cursor.Current = Cursors.Default;
                    }
                }

                Grid.RefreshDataSource();
            }
        }

        /// <summary>
        /// Deletes the selected rows.
        /// </summary>
        public void DeleteSelectedRows()
        {
            if (!m_IsEditable)
                return;

            if (!m_AllowDelete)
                return;

            if (m_RecordType == RecordType.Child)
                throw new NotSupportedException();

            List<TBusinessObject> list = GetSelectedObjects();

            if (list != null && list.Count != 0)
            {
                if (MessageBox.Show("Delete selected row(s)?", "", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
                    return;

                Cursor.Current = Cursors.WaitCursor;

                GridView.BeginUpdate();
                try
                {
                    foreach (TBusinessObject item in list)
                    {
                        item.Delete();
                        DataSource.Remove(item);
                    }
                }
                finally
                {
                    GridView.EndUpdate();
                }

                Cursor.Current = Cursors.Default;
            }
        }

        /// <summary>
        /// Deletes the selected rows. The <paramref name="deleteListMethod"/> delegate is called to perform the deletion.
        /// </summary>
        /// <param name="deleteListMethod">The delete list method.</param>
        public void DeleteSelectedRows(DeleteListDelegate deleteListMethod)
        {
            Guard.ArgumentNotNull(deleteListMethod, "deleteListMethod");

            if (!m_IsEditable)
                return;

            if (!m_AllowDelete)
                return;

            List<TBusinessObject> list = GetSelectedObjects();

            if (list != null && list.Count != 0)
            {
                if (MessageBox.Show("Delete selected row(s)?", "", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
                    return;

                Cursor.Current = Cursors.WaitCursor;

                deleteListMethod(list);

                GridView.BeginUpdate();
                try
                {
                    foreach (TBusinessObject item in list)
                        DataSource.Remove(item);
                }
                finally
                {
                    GridView.EndUpdate();
                }

                Cursor.Current = Cursors.Default;
            }
        }

        /// <summary>
        /// Creates a new record, adds it to the DataSource, selects the record, and calls up the <typeparamref name="TForm"/>.
        /// The <typeparamref name="TForm"/> must have a constructor which takes <typeparamref name="TBusinessObject"/> as its
        /// only parameter.
        /// </summary>
        /// <typeparam name="TForm">The form type.</typeparam>
        public void CreateNewRecord<TForm>(RecordType recordType)
            where TForm : Form
        {
            if (!m_IsEditable)
                return;

            if (!m_AllowAdd)
                return;

            CreateNewRecord(GetFormConstructor<TForm>(), recordType);
        }

        private static ConstructorInfo GetFormConstructor<TForm>()
            where TForm : Form
        {
            ConstructorInfo constructor = typeof(TForm).GetConstructor(new[] { typeof(TBusinessObject), typeof(RecordType) });

            if (constructor == null)
                throw new Exception(string.Format("Edit form '{0}' does not contain a constructor which takes ({1}, {2})'.", typeof(TForm).FullName, typeof(TBusinessObject).FullName, typeof(RecordType).FullName));

            return constructor;
        }

        private void CreateNewRecord(ConstructorInfo constructor, RecordType recordType)
        {
            Guard.ArgumentNotNull(constructor, "constructor");

            if (!m_IsEditable)
                return;

            if (!m_AllowAdd)
                return;

            Cursor.Current = Cursors.WaitCursor;

            if (DataSource == null)
                DataSource = new BindingList<TBusinessObject>();

            TBusinessObject item = new TBusinessObject();
            DataSource.Add(item);

            bool remove = true;
            try
            {
                SelectSingleRow(item);

                using (Form form = (Form)constructor.Invoke(new object[] { item, recordType }))
                {
                    Cursor.Current = Cursors.Default;
                    remove = (form.ShowDialog() != DialogResult.OK);
                }
            }
            finally
            {
                if (remove)
                    DataSource.Remove(item);
            }
        }

        /// <summary>
        /// Edits the selected record.
        /// The <typeparamref name="TForm"/> must have a constructor which takes <typeparamref name="TBusinessObject"/> as its
        /// only parameter.
        /// </summary>
        /// <typeparam name="TForm">The form type.</typeparam>
        /// <param name="recordType">The record type.</param>
        public void EditSelectedRecord<TForm>(RecordType recordType)
            where TForm : Form
        {
            if (!m_IsEditable)
                return;

            EditSelectedRecord(GetFormConstructor<TForm>(), recordType);
        }

        private void EditSelectedRecord(ConstructorInfo constructor, RecordType recordType)
        {
            Guard.ArgumentNotNull(constructor, "constructor");

            if (!m_IsEditable)
                return;

            Cursor.Current = Cursors.WaitCursor;

            TBusinessObject item = GetSelectedObject();
            if (item == null)
            {
                Cursor.Current = Cursors.Default;
                return;
            }

            using (Form form = (Form)constructor.Invoke(new object[] { item, recordType }))
            {
                Cursor.Current = Cursors.Default;
                form.ShowDialog();
            }
        }

        /// <summary>
        /// Adds a child relationship.
        /// </summary>
        /// <typeparam name="TParent">The type of the parent.</typeparam>
        /// <param name="bindableObject">The bindable object.</param>
        /// <param name="expression">The expression yielding the child object.</param>
        public override void AddChildRelationship<TParent>(TParent bindableObject, Expression<Func<TParent, BindingList<TBusinessObject>>> expression)
        {
            m_RecordType = RecordType.Child;

            base.AddChildRelationship(bindableObject, expression);
        }

        /// <summary>
        /// Adds a child relationship.
        /// </summary>
        /// <typeparam name="TParent">The type of the parent.</typeparam>
        /// <typeparam name="TEditForm">The type of the edit form.</typeparam>
        /// <param name="bindableObject">The bindable object.</param>
        /// <param name="expression">The expression yielding the child object.</param>
        public void AddChildRelationship<TParent, TEditForm>(TParent bindableObject, Expression<Func<TParent, BindingList<TBusinessObject>>> expression)
            where TParent : class, IBusinessObjectLinq
            where TEditForm : Form
        {
            AddChildRelationship<TParent, TEditForm>(bindableObject, expression, true);
        }

        /// <summary>
        /// Adds a child relationship.
        /// </summary>
        /// <typeparam name="TParent">The type of the parent.</typeparam>
        /// <typeparam name="TEditForm">The type of the edit form.</typeparam>
        /// <param name="bindableObject">The bindable object.</param>
        /// <param name="expression">The expression yielding the child object.</param>
        /// <param name="addEditButtons">if set to <c>true</c>, add/edit buttons will be added to the grid.</param>
        public void AddChildRelationship<TParent, TEditForm>(TParent bindableObject, Expression<Func<TParent, BindingList<TBusinessObject>>> expression, bool addEditButtons)
            where TParent : class, IBusinessObjectLinq
            where TEditForm : Form
        {
            AddChildRelationship<TParent, TEditForm>(bindableObject, expression, addEditButtons, null, null, null);
        }

        /// <summary>
        /// Adds a child relationship.
        /// </summary>
        /// <typeparam name="TParent">The type of the parent.</typeparam>
        /// <typeparam name="TEditForm">The type of the edit form.</typeparam>
        /// <param name="bindableObject">The bindable object.</param>
        /// <param name="expression">The expression yielding the child object.</param>
        /// <param name="addEditButtons">if set to <c>true</c>, add/edit buttons will be added to the grid.</param>
        /// <param name="newButton">The new button (optional).</param>
        /// <param name="editButton">The edit button (optional).</param>
        /// <param name="deleteButton">The delete button (optional).</param>
        public void AddChildRelationship<TParent, TEditForm>(TParent bindableObject, Expression<Func<TParent, BindingList<TBusinessObject>>> expression, bool addEditButtons, Control newButton, Control editButton, Control deleteButton)
            where TParent : class, IBusinessObjectLinq
            where TEditForm : Form
        {
            m_RecordType = RecordType.Child;

            if (addEditButtons)
                AddEditButtons();

            SetEditForm<TEditForm>(newButton, editButton, deleteButton);

            base.AddChildRelationship(bindableObject, expression);
        }

        /// <summary>
        /// Sets the edit form.
        /// </summary>
        /// <typeparam name="TEditForm">The type of the edit form.</typeparam>
        public void SetEditForm<TEditForm>()
            where TEditForm : Form
        {
            if (!m_IsEditable)
                return;

            SetEditForm<TEditForm>(null, null, null);
        }

        /// <summary>
        /// Sets the edit form.
        /// </summary>
        /// <typeparam name="TEditForm">The type of the edit form.</typeparam>
        /// <param name="newButton">The New button.</param>
        /// <param name="editButton">The Edit button.</param>
        /// <param name="deleteButton">The Delete button.</param>
        public void SetEditForm<TEditForm>(Control newButton, Control editButton, Control deleteButton)
            where TEditForm : Form
        {
            m_FormConstructor = GetFormConstructor<TEditForm>();

            if (m_RecordType == null)
                m_RecordType = RecordType.Parent;

            NewRecord += BaseEditGridController_NewRecord;
            EditRecord += BaseEditGridController_EditRecord;

            if (newButton != null)
                newButton.Click += NewButton_Click;
            if (editButton != null)
                editButton.Click += EditButton_Click;
            if (deleteButton != null)
                deleteButton.Click += DeleteButton_Click;
        }

        private void DeleteButton_Click(object sender, EventArgs e)
        {
            if (!m_IsEditable)
                return;

            if (!m_AllowDelete)
                return;

            InternalDeleteRecord();
        }

        private void BaseEditGridController_EditRecord(object sender, EditRecordEventArgs<TBusinessObject> args)
        {
            if (!m_IsEditable)
                return;

            Guard.ArgumentNotNull(m_RecordType, "m_RecordType");

            Cursor.Current = Cursors.WaitCursor;
            using (Form form = (Form)m_FormConstructor.Invoke(new object[] { args.Item, m_RecordType.Value }))
            {
                Cursor.Current = Cursors.Default;
                form.ShowDialog();
                args.Handled = true;
            }

            Grid.RefreshDataSource();
        }

        private void EditButton_Click(object sender, EventArgs e)
        {
            Guard.ArgumentNotNull(m_RecordType, "m_RecordType");

            if (!m_IsEditable)
                return;

            EditSelectedRecord(m_FormConstructor, m_RecordType.Value);
        }

        private void NewButton_Click(object sender, EventArgs e)
        {
            Guard.ArgumentNotNull(m_RecordType, "m_RecordType");

            if (!m_IsEditable)
                return;

            if (!m_AllowAdd)
                return;

            CreateNewRecord(m_FormConstructor, m_RecordType.Value);
        }

        private void BaseEditGridController_NewRecord(object sender, NewRecordEventArgs<TBusinessObject> args)
        {
            Guard.ArgumentNotNull(m_RecordType, "m_RecordType");

            if (!m_IsEditable)
                return;

            if (!m_AllowAdd)
                return;

            Cursor.Current = Cursors.WaitCursor;
            using (Form form = (Form)m_FormConstructor.Invoke(new object[] { args.Item, m_RecordType.Value }))
            {
                Cursor.Current = Cursors.Default;
                args.Remove = (form.ShowDialog() != DialogResult.OK);
                args.Handled = true;
            }

            Grid.RefreshDataSource();
        }

        /// <summary>
        /// Disables edit (including add, delete).
        /// </summary>
        public void DisableEdit()
        {
            m_IsEditable = false;
        }

        /// <summary>
        /// Disables add.
        /// </summary>
        public void DisableAdd()
        {
            m_AllowAdd = false;
        }

        /// <summary>
        /// Disables delete.
        /// </summary>
        public void DisableDelete()
        {
            m_AllowDelete = false;
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public override void Dispose()
        {
            Grid.EmbeddedNavigator.ButtonClick -= EmbeddedNavigator_ButtonClick;
            Grid.RowDoubleClick -= m_GridControl_RowDoubleClick;

            base.Dispose();
        }
    }
}
